/*

    Licensed under the Apache License, Version 2.0 (the "License"); 
    you may not use this file except in compliance with the License. 
    You may obtain a copy of the License at 
    http://www.apache.org/licenses/LICENSE-2.0 
    Unless required by applicable law or agreed to in writing, software 
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
    See the License for the specific language governing permissions and 
    limitations under the License.
      
 */

package jforex.impl;

import java.awt.Image;
import java.awt.Toolkit;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Currency;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.Icon;
import javax.swing.ImageIcon;

import jforex.api.IBox;
import jforex.api.ISettings;

import com.dukascopy.api.IBar;
import com.dukascopy.api.IConsole;
import com.dukascopy.api.IContext;
import com.dukascopy.api.IEngine;
import com.dukascopy.api.IHistory;
import com.dukascopy.api.IOrder;
import com.dukascopy.api.Instrument;
import com.dukascopy.api.JFException;
import com.dukascopy.api.OfferSide;
import com.dukascopy.api.Period;
import com.dukascopy.api.IEngine.OrderCommand;
import com.dukascopy.api.IOrder.State;

/**
 * 
 * Implementation of IBox.
 * All kind of useful methods for helping to create JForex strategies
 * 
 */
public class JFToolBox implements IBox {

    private IContext context = null;

    private IEngine engine = null;

    private IConsole console = null;

    private IHistory history = null;

    private CurrConverter converter = null;

    static DecimalFormat dfPrint = new DecimalFormat("#,##0.##########");

    public JFToolBox(IContext context) {
        if (context != null) {
            this.context = context;
            this.engine = context.getEngine();
            this.history = context.getHistory();
            this.console = context.getConsole();
            this.converter = new CurrConverter(context);
        }
    }

    // public func.

    /**
     * Return directory where reading and writing allowed. Usually "~/My Documents/My Strategies/files"
     */
    public File getFilesDir() {
        return context.getFilesDir();
    }

    /**
     * 
     * @param scannerName
     *            - File name located in "files" directory. With default
     *            delimiter as line return
     * @return Scanner - scanned from the specified file
     */
    public Scanner getScanner(String scannerName) {
        return getScanner(scannerName, "\r\n|[\n\r\u2028\u2029\u0085]");
    }

    /**
     * 
     * @param scannedFile
     *            - File name located in "files" directory. With default
     *            delimiter as line return
     * @return Scanner - scanned from the specified file
     */
    public Scanner getScanner(File scannedFile) {

        return getScanner(scannedFile, "\r\n|[\n\r\u2028\u2029\u0085]");
    }

    /**
     * 
     * @param scannerName
     *            - File name located in "files" directory.
     * @param delimiterPattern
     *            - delimiter regexp
     * @return Scanner - scanned from the specified file
     */
    public Scanner getScanner(String scannerName, String delimiterPattern) {
        Scanner rc = null;
        if (scannerName == null || scannerName.indexOf(File.separator) != -1 || scannerName.indexOf(File.pathSeparator) != -1) {
            return rc;
        }
        File theFile = new File(getFilesDir() + File.separator + scannerName);
        rc = getScanner(theFile, delimiterPattern);

        return rc;

    }

    /**
     * 
     * @param theFile
     *            - File to be scanned.
     * @param delimiterPattern
     *            - delimiter regexp
     * @return Scanner - scanned from the specified file
     */
    public Scanner getScanner(File theFile, String delimiterPattern) {
        Scanner rc = null;
        if (theFile == null || !theFile.isFile() || !theFile.exists()) {
            return rc;
        }

        try {
            rc = new Scanner(theFile);
            if (delimiterPattern != null) {
                rc.useDelimiter(delimiterPattern);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return rc;

    }

    /**
     * Helper function requesting specified number of latest bars. There is
     * important note: less index = newest bar
     * 

     * 
     * @param currentPeriod
     *            - period of requested bars
     * @param bars
     *            - number of bars requested
     * @param takeUnformedBar
     *            - if true, unformed bar also returns     
     * @return - latest N bars
     * @throws JFException
     */
    public List<IBar> getLastBars(Instrument instrument, Period period, OfferSide offerSide, int bars, boolean takeUnformedBar) throws JFException {
        long lastTickTime = history.getLastTick(instrument).getTime();

        long prevBarStart = history.getPreviousBarStart(period, lastTickTime);
        long timeRange = period.getInterval() * (bars + 1);
        long oldestBarStart = history.getBarStart(period, lastTickTime - timeRange);

        List<IBar> rc = history.getBars(instrument, period, offerSide, oldestBarStart, prevBarStart);
        // here we getting on progress bar
        if (takeUnformedBar) {
            IBar onProgressBar = history.getBar(instrument, period, offerSide, 0);
            if (rc.get(rc.size() - 1).getTime() != onProgressBar.getTime()) {
                // on progress bar need to be added
                rc.add(onProgressBar);
            }
        }
        while (rc.size() > bars) {
            rc.remove(0);
        }

        if (rc.size() != bars) {
            throw new JFException("getLastBars() assertion error [" + rc.size() + "]");
        }
        Collections.reverse(rc);
        return rc;
    }

    /**
     * Rounding function
     * 
     * @param value
     * @param precision
     * @return - rounded value
     */
    public double round(double value, int precision) {
        if (Double.isNaN(value)) {
            return value;
        }
        boolean negative = false;
        if (value < 0) {
            negative = true;
            value = -value;
        }
        if (value == 0) {
            return value;
        }
        double multiplier = 1;
        while (precision > 0) {
            multiplier *= 10;
            --precision;
        }
        while (precision < 0 && value * multiplier / 10 >= 1) {
            multiplier /= 10;
            ++precision;
        }
        while (value * multiplier < 1) {
            multiplier *= 10;
        }
        value *= multiplier;
        long longValue = (long) (value + 0.5d);
        value = ((double) longValue) / multiplier;
        return negative ? -value : value;
    }

    @Override
    public double roundHalfPip(double price) {
        int pipsMultiplier = price <= 20 ? 10000 : 100;
        int rounded = (int) (price * pipsMultiplier * 10 + 0.5);
        rounded *= 2;
        rounded = (int) (((double) rounded) / 10d + 0.5d);
        price = ((double) rounded) / 2d;
        price /= pipsMultiplier;
        return price;

    }

    public void print(Object... str) {
        console.getOut().println(multiargToString("", str));
    }

    public String multiargToString(String delim, Object... str) {
        StringBuilder builder = new StringBuilder();
        for (Object object : str) {
            if (object instanceof Number) {
                Number number = (Number) object;
                if (Math.abs(number.doubleValue()) > 1E-6) {
                    builder.append(delim + dfPrint.format(object));
                } else {
                    builder.append(delim + object);
                }
            } else {
                builder.append(delim + object);
            }
        }
        return builder.substring(delim.length());
    }

    public double convertMoney(double amount, Currency sourceCurrency, Currency targetCurrency) throws JFException {
        return converter.convert(amount, sourceCurrency, targetCurrency);
    }

    /**
     * 
     * @param order
     *            - Order for calculation
     * @return profit in pips. Valid for CLOSES or FILLED orders. Amount doesn't
     *         taken into consideration
     * @throws JFException
     */

    public double calculateProfitPips(IOrder order) throws JFException {
        return calculateProfitPips(order.getOpenPrice(), orderBasePriceFormCalculations(order), order.isLong(), order.getInstrument().getPipValue());
    }

    /**
     * 
     * @param openPrice
     * @param closePrice
     * @param direction
     *            - 1 for long position , -1 for short
     * @param pipValue
     *            - pip value (example 0.0001)
     * @return
     */
    public double calculateProfitPips(double openPrice, double closePrice, boolean isLong, double pipValue) {
        double profitPips = (closePrice - openPrice) * (isLong ? 1 : -1);
        profitPips = profitPips / (pipValue);
        return round(profitPips, 7);
    }

    public double calculateProfitMoney(IOrder order) throws JFException {
        return calculateProfitMoney(order.getOpenPrice(), orderBasePriceFormCalculations(order), order.isLong(), order.getAmount());
    }

    public double calculateProfitMoney(double openPrice, double closePrice, boolean isLong, double amountInMil) {

        double profit = (closePrice - openPrice) * (isLong ? 1 : -1);
        profit = profit * amountInMil * 1E6;
        return round(profit, 7);
    }

    private double orderBasePriceFormCalculations(IOrder order) throws JFException {
        double basePrice = 0;
        if (order.getState() == IOrder.State.FILLED) {
            basePrice = converter.getLastMarketPrice(order.getInstrument(), order.isLong() ? OfferSide.BID : OfferSide.ASK);
            if (basePrice == 0) {
                throw new JFException("No price for " + order.getInstrument());
            }
        } else if (order.getState() == IOrder.State.CLOSED) {
            basePrice = order.getClosePrice();
            if (basePrice == 0) {
                basePrice = Double.NaN;
            }
        } else {
            basePrice = Double.NaN;
        }
        return basePrice;

    }

    /**
     * Calculates current exposure for specified instrument
     * 
     * @param instrument
     *            : can not be null
     * @throws JFException
     */
    public double getCurrentExposure(Instrument instr) throws JFException {
        List<IOrder> list = engine.getOrders(instr);
        double rc = 0;
        for (IOrder order : list) {
            if (order.getState() == IOrder.State.FILLED) {
                double amo = order.getAmount();
                if (order.isLong()) {
                    rc = rc + amo;
                } else {
                    rc = rc - amo;
                }
            }
        }
        return rc;

    }

    /**
     * This helper function return orders with specified instrument and state
     * 
     * @param instrument
     *            - needed instrument. If null : all instruments will be
     *            returned
     * @param state
     *            - needed state. If null all states will be returned * @param
     *            magic - order magic number. If 0 or less then 0 - magic
     *            numbers will not will not be checked
     * 
     * @return - list of orders, return empty list if not found
     * @throws JFException
     */
    public List<IOrder> getOrders(Instrument instrument, IOrder.State state, Boolean isLong, int magic) throws JFException {
        List<IOrder> rc = new ArrayList<IOrder>();
        if (instrument == null) {
            for (IOrder order : engine.getOrders()) {
                if (state == null || state == order.getState()) {
                    if (magic <= 0 || magic == getMagicNumber(order)) {
                        if (isLong == null || isLong == order.isLong()) {
                            rc.add(order);
                        }
                    }
                }
            }
        } else {
            for (IOrder order : engine.getOrders(instrument)) {
                if (state == null || state == order.getState()) {
                    if (magic <= 0 || magic == getMagicNumber(order)) {
                        if (isLong == null || isLong == order.isLong()) {
                            rc.add(order);
                        }
                    }
                }
            }
        }
        return rc;
    }

    public List<IOrder> getOrders(Instrument instrument, IOrder.State state, int magic) throws JFException {
        return getOrders(instrument, state, null, 0);
    }

    public List<IOrder> getOrders(Instrument instrument, State state) throws JFException {
        return getOrders(instrument, state, 0);

    }

    private static Random random = new Random();

    private String MAGIC_PREFIX = "M";

    /**
     * 
     * @return random generated label
     */
    private static String generateRandom() {
        String label = "";
        while (label.length() < 10) {
            label = label + Integer.toString(random.nextInt(100000000), 32);
        }
        label = label.substring(0, 9);

        return label;

    }

    public static void main(String[] args) throws JFException {
        JFToolBox box = new JFToolBox(null);
        /*
         long l1 = System.currentTimeMillis();
         for (int i = 0; i < 1000000; i++) {
         box.isInTimeRange(System.currentTimeMillis(), "16:00-06:00");
         }
         System.out.println((System.currentTimeMillis()-l1)+" / "+box.isInTimeRange(System.currentTimeMillis(), "15:00-15:00"));
         */
        for (int i = 0; i < 10000; i++) {
            System.out.println(generateRandom());
        }

    }

    public String generateLabel(int magic) {
        String label = MAGIC_PREFIX + magic + "x" + generateRandom();
        return label;
    }

    public int getMagicNumber(IOrder order) {
        int rc = 0;
        String label = order.getLabel();
        if (label.startsWith(MAGIC_PREFIX) && label.indexOf("x") != -1) {
            try {
                label = label.substring(MAGIC_PREFIX.length(), label.indexOf("x"));
                rc = Integer.parseInt(label);
            } catch (Exception e) {
                System.out.println("LABEL=" + label);
                e.printStackTrace();
            }
        } else {
            //System.out.println("getMagicNumber called for LABEL=" + label);
        }
        return rc;

    }

    public List<IOrder> mergeAllInstrumentsOrdersSync(int magic, List<IOrder> positions) throws JFException {

        Map<Instrument, List<IOrder>> allByInstruments = new HashMap<Instrument, List<IOrder>>();

        List<IOrder> rc = new ArrayList<IOrder>();
        if (positions != null && positions.size() > 1) {

            for (IOrder iOrder : positions) {
                if (iOrder.getState() == IOrder.State.FILLED) {
                    //removes SLTP
                    if (iOrder.getStopLossPrice() > 0) {
                        iOrder.setStopLossPrice(0);
                        iOrder.waitForUpdate(5000);
                    }
                    if (iOrder.getTakeProfitPrice() > 0) {
                        iOrder.setTakeProfitPrice(0);
                        iOrder.waitForUpdate(5000);
                    }
                    //store
                    Instrument instr = iOrder.getInstrument();
                    List<IOrder> orders = allByInstruments.get(instr);
                    if (orders == null) {
                        orders = new ArrayList<IOrder>();
                        allByInstruments.put(instr, orders);
                    }
                    //right, second check
                    if (iOrder.getState() == IOrder.State.FILLED) {
                        orders.add(iOrder);
                    }
                }
            }
            for (List<IOrder> orders : allByInstruments.values()) {
                if (orders.size() > 1) {
                    IOrder iOrder = engine.mergeOrders(generateLabel(magic), orders.toArray(new IOrder[0]));
                    if (iOrder.getState() == IOrder.State.CREATED) {
                        iOrder.waitForUpdate(5000);
                    }
                    rc.add(iOrder);
                } else if (orders.size() == 1) {
                    rc.add(orders.get(0));
                }
            }

        }
        return rc;
    }

    public IOrder mergeOrdersSync(String label, List<IOrder> positions) throws JFException {

        IOrder rc = null;
        List<IOrder> ordersToMerge = new ArrayList<IOrder>();
        if (positions != null && positions.size() > 1) {
            for (IOrder iOrder : positions) {
                if (iOrder.getState() == IOrder.State.FILLED) {
                    //removes SLTP
                    if (iOrder.getStopLossPrice() > 0) {
                        iOrder.setStopLossPrice(0);
                        iOrder.waitForUpdate(5000);
                    }
                    if (iOrder.getTakeProfitPrice() > 0) {
                        iOrder.setTakeProfitPrice(0);
                        iOrder.waitForUpdate(5000);
                    }
                    ordersToMerge.add(iOrder);
                }
            }

            if (ordersToMerge.size() >= 2) {
                IOrder iOrder = engine.mergeOrders(label, ordersToMerge.toArray(new IOrder[0]));
                if (iOrder.getState() == IOrder.State.CREATED) {
                    iOrder.waitForUpdate(5000);
                }
                rc = iOrder;
            } else if (ordersToMerge.size() == 1) {
                rc = ordersToMerge.get(0);
            }

        }
        return rc;
    }

    /**
     * Function pause for specified period in milliseconds
     * 
     * @param milliseconds
     */
    public void sleep(int milliseconds) {
        if (context.getEngine().getType() != IEngine.Type.TEST) {
            try {
                Thread.sleep(milliseconds);
            } catch (Exception e) {

                e.printStackTrace();
            }
        }

    }

    private Map<String, HighAvailabilityStore> highStores = new HashMap<String, HighAvailabilityStore>();

    public ISettings getSettings(String settingsName) {

        if (settingsName == null || settingsName.length() == 0) {
            settingsName = "global";
        }
        File lf = new File(getFilesDir() + File.separator + settingsName);
        String lockFileName = lf.getAbsolutePath();
        HighAvailabilityStore store = highStores.get(lockFileName);
        if (store == null) {
            store = new HighAvailabilityStore(lockFileName);
        }
        highStores.put(settingsName, store);
        return store;

    }

    private AudioPlayer audioPlayer = new AudioPlayer();

    @Override
    public boolean playSound(String soundName) {

        String sound = getFilesDir() + File.separator + soundName;

        return audioPlayer.play(sound);

    }

    private int turboPipe(InputStream inputStream, OutputStream outputStream) {

        if (inputStream == null || outputStream == null)
            return 0;

        byte[] buffer = new byte[4 * 1024];
        int bytes_read;
        int counter = 0;
        try {
            while (true) {
                bytes_read = inputStream.read(buffer);
                if (bytes_read == -1) {
                    break;
                }
                outputStream.write(buffer, 0, bytes_read);
                counter += bytes_read;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        }
        return counter;
    }

    @Override
    public Image loadImage(String path) {
        Image rc = null;
        rc = Toolkit.getDefaultToolkit().createImage(loadResource(path));
        return rc;
    }

    @Override
    public byte[] loadResource(String path) {
        byte[] rc = null;
        try {
            InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
            ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
            turboPipe(inputStream, arrayOutputStream);
            rc = arrayOutputStream.toByteArray();
            inputStream.close();
        } catch (Throwable e) {
            System.out.println(e.getMessage());
        }
        return rc;
    }

    @Override
    public Icon loadIcon(final String path) {
        Icon icon = new ImageIcon(loadImage(path));
        return icon;
    }

    public IOrder submitOrderSync(Instrument instrument, IEngine.OrderCommand cmd, double lot, double price, double slippage, int sl, int tp, int magic) throws JFException {
        return submitOrderSync(instrument, cmd, lot, price, slippage, sl, tp, magic, null);
    }

    public IOrder submitOrderSync(Instrument instrument, IEngine.OrderCommand cmd, double lot, double price, double slippage, int sl, int tp, int magic, String comment) throws JFException {

        double slPrice = 0;
        double tpPrice = 0;

        if (price > 0) {
            if (sl > 0) {
                slPrice = calculateStopLoss(instrument, cmd, price, sl);
            }
            if (tp > 0) {
                tpPrice = calculateTakeProfit(instrument, cmd, price, tp);
            }
        }

        boolean isMarket = cmd == IEngine.OrderCommand.BUY || cmd == IEngine.OrderCommand.SELL;
        IOrder order = engine.submitOrder(generateLabel(magic), instrument, cmd, lot, price, slippage, slPrice, tpPrice, 0L, comment);
        order.waitForUpdate(5000);
        if (isMarket) {
            for (int i = 0; i < 10; i++) {

                if (order.getState() == IOrder.State.FILLED || order.getState() == IOrder.State.CLOSED || order.getState() == IOrder.State.CANCELED) {
                    // ok, proceed
                    break;
                } else {
                    if (i > 0) {
                        System.out.println("IBox waiting fill from [" + order.getState() + "][" + i + "]");
                    }
                    order.waitForUpdate(5000);
                }
            }
        } else {
            if (order.getState() == IOrder.State.OPENED || order.getState() == IOrder.State.FILLED || order.getState() == IOrder.State.CLOSED || order.getState() == IOrder.State.CANCELED) {
                // ok, proceed
            } else {
                System.out.println("IBox waiting open for [" + order.getState() + "]");
                order.waitForUpdate(5000);
            }
        }

        //Make SL and TP for marked orders
        if (order.getState() == IOrder.State.FILLED && price <= 0) {
            if (sl > 0) {
                slPrice = calculateStopLoss(instrument, cmd, order.getOpenPrice(), sl);
                order.setStopLossPrice(slPrice);
                order.waitForUpdate(5000);
            }
            if (tp > 0) {
                tpPrice = calculateTakeProfit(instrument, cmd, order.getOpenPrice(), tp);
                order.setTakeProfitPrice(tpPrice);
                order.waitForUpdate(5000);
            }
        }

        return order;
    }

    public double calculateStopLoss(Instrument currentInstrument, OrderCommand cmd, double openPrice, int pips) throws JFException {

        // managing STOP LOSS
        double rc = 0;
        boolean isLong = cmd.isLong();

        if (openPrice <= 0) {
            throw new JFException("Unable to calculate SL for 0 price");
            // openPrice = isLong ?
            // history.getLastTick(currentInstrument).getAsk() :
            // history.getLastTick(currentInstrument).getBid();
        }
        if (isLong) {
            rc = openPrice - (pips * currentInstrument.getPipValue());
        } else {
            rc = openPrice + (pips * currentInstrument.getPipValue());
        }
        rc = round(rc, 5);

        return rc;
    }

    public double calculateTakeProfit(Instrument currentInstrument, OrderCommand cmd, double openPrice, int pips) throws JFException {

        // managing TAKE PROFIT
        double rc = 0;
        boolean isLong = cmd.isLong();

        if (openPrice <= 0) {
            throw new JFException("Unable to calculate TP for 0 price");
            // openPrice = isLong ?
            // history.getLastTick(currentInstrument).getAsk() :
            // history.getLastTick(currentInstrument).getBid();
        }

        if (isLong) {
            rc = openPrice + (pips * currentInstrument.getPipValue());
        } else {
            rc = openPrice - (pips * currentInstrument.getPipValue());
        }
        rc = round(rc, 5);

        return rc;
    }

    public IOrder closeOrderSync(IOrder order) throws JFException {
        order.close();
        order.waitForUpdate(5000);
        return order;
    }

    public void closeAllSync() throws JFException {
        List<IOrder> positions = getOrders(null, IOrder.State.FILLED);
        List<IOrder> entrys = getOrders(null, IOrder.State.OPENED);
        closeAllSync(positions);
        closeAllSync(entrys);
    }

    public void closeAllSync(Instrument instrument) throws JFException {
        List<IOrder> positions = getOrders(instrument, IOrder.State.FILLED);
        List<IOrder> entrys = getOrders(instrument, IOrder.State.OPENED);
        closeAllSync(positions);
        closeAllSync(entrys);
    }

    public void closeAllSync(Instrument instrument, int magic) throws JFException {
        List<IOrder> positions = getOrders(instrument, IOrder.State.FILLED, magic);
        List<IOrder> entrys = getOrders(instrument, IOrder.State.OPENED, magic);
        closeAllSync(positions);
        closeAllSync(entrys);
    }

    public void closeAllSync(List<IOrder> orders) throws JFException {
        for (IOrder order : orders) {
            try {
                if (order.getState() == IOrder.State.OPENED || order.getState() == IOrder.State.FILLED) {
                    closeOrderSync(order);
                }
            } catch (JFException e) {
                e.printStackTrace();
            }
        }
    }

    private Instrument lastSureSignedInstrument = Instrument.EURUSD;

    public long getLastTime() {
        long rc = 0;

        try {
            rc = history.getLastTick(lastSureSignedInstrument).getTime();
        } catch (JFException e) {

        }
        // we have wrong signed instrument. finding another
        if (rc == 0) {
            for (Instrument instrument : Instrument.values()) {
                try {
                    rc = history.getLastTick(instrument).getTime();
                    lastSureSignedInstrument = instrument;
                    break;
                } catch (JFException e) {

                }
            }
        }
        return rc;

    }

    // Temporary evaluation check.
    private SimpleDateFormat evaluationDateFormat = new SimpleDateFormat("yyyy.MM.dd");
    private boolean evaluationChecked = false;

    public boolean evaluation(boolean onlyDemo, String endDate) {
        if (evaluationChecked) {
            return true;
        }
        // TEST always allowed
        if (context.getEngine().getType() != IEngine.Type.TEST) {

            if (onlyDemo) {
                print("Service evaluation. Working only in DEMO mode.");
                if (context.getEngine().getType() == IEngine.Type.LIVE) {
                    print(getClass().getSimpleName() + " stopped.");
                    context.stop();
                    return false;
                }
            }
            if (endDate != null) {
                print("Service evaluation. Working only until " + endDate);
                try {
                    long evtime = evaluationDateFormat.parse(endDate).getTime();

                    if (getLastTime() > evtime) {
                        print(getClass().getSimpleName() + " expired.");
                        context.stop();
                        return false;
                    }
                } catch (ParseException e) {
                }
            }
        }
        print("Evaluation version. Proceed.");
        evaluationChecked = true;
        return true;
    }

    Pattern timeePattern = Pattern.compile("([0-2][0-9]):([0-5][0-9])");

    public boolean isBeforeTime(long nowTime, String timeCompareTo) throws JFException {
        boolean rc = false;
        Matcher matcher = timeePattern.matcher(timeCompareTo);
        long lTo = 0;
        if (matcher.find()) {
            long hrsTo = Long.parseLong(matcher.group(1));
            long minTo = Long.parseLong(matcher.group(2));
            lTo = (hrsTo * 3600000) + minTo * 60000;
        } else {
            throw new JFException("TimeRange format incorrect");
        }
        long nowIntraDay = nowTime % 86400000;
        if (nowIntraDay < lTo) {
            rc = true;
        }
        return rc;
    }

    Pattern timeRangePattern = Pattern.compile("([0-2][0-9]):([0-5][0-9])-([0-2][0-9]):([0-5][0-9])");

    @Override
    public boolean isInTimeRange(long nowTime, String timeRange) throws JFException {

        if ("-".equals(timeRange)) {
            return true;
        }

        Matcher matcher = timeRangePattern.matcher(timeRange);

        long lFrom = 0;
        long lTo = 0;

        if (matcher.find()) {
            long hrsFrom = Long.parseLong(matcher.group(1));
            long minFrom = Long.parseLong(matcher.group(2));

            long hrsTo = Long.parseLong(matcher.group(3));
            long minTo = Long.parseLong(matcher.group(4));

            lFrom = (hrsFrom * 3600000) + minFrom * 60000;
            lTo = (hrsTo * 3600000) + minTo * 60000;

        } else {
            throw new JFException("TimeRange format incorrect");
        }

        if (lFrom >= lTo) {
            throw new JFException("TimeRange incorrect");
        }

        // 25:56

        boolean rc = false;

        long nowIntraDay = nowTime % 86400000;

        if (lFrom < lTo) {
            if (nowIntraDay >= lFrom && nowIntraDay <= lTo) {
                rc = true;
            }
        }

        return rc;
    }

    @Override
    public IBar[] getNBars(Instrument symbol, Period convertedPeriod, OfferSide offerSide, int start, int count) throws JFException {
        IBar[] bars = new IBar[count];
        for (int i = 0; i < count; i++) {
            IBar bar = history.getBar(symbol, convertedPeriod, offerSide, i + start);
            bars[i] = bar;
        }

        return bars;
    }

    @Override
    public double weightedPrice(List<IOrder> orders) throws JFException {
        double rc = 0;
        double amoPrimarySum = 0;
        double amoSecondarySum = 0;
        Instrument instrument = null;
        for (IOrder iOrder : orders) {
            if (iOrder.getOpenPrice() <= 0) {
                continue;
            }
            if (instrument == null) {
                instrument = iOrder.getInstrument();
            } else {
                if (instrument != iOrder.getInstrument()) {
                    throw new JFException("weightedPrice can not be calculated for different instruments.");
                }
            }

            double amoPrimary = iOrder.getAmount();
            double amoSecondary = -amoPrimary * iOrder.getOpenPrice();
            amoPrimarySum = amoPrimarySum + amoPrimary;
            amoSecondarySum = amoSecondarySum + amoSecondary;
        }
        if (amoSecondarySum != 0) {
            rc = Math.abs(amoSecondarySum / amoPrimarySum);
            rc = round(rc, 7);
        }
        return rc;
    }

    @Override
    public double getExposureByInstrument(Instrument instrument) throws JFException {
        return getExposureByInstrument(instrument, engine.getOrders());
    }

    @Override
    public double getExposureByInstrument(Instrument instrument, List<IOrder> orders) {
        double rc = 0;
        for (IOrder iOrder : orders) {
            if (iOrder.getInstrument() == instrument && iOrder.getState() == IOrder.State.FILLED) {
                rc = rc + iOrder.getAmount();
            }
        }
        rc = round(rc, 2);
        return rc;
    }

    @Override
    public double getExposureByCurrency(Currency currency) throws JFException {
        return getExposureByCurrency(currency, engine.getOrders());
    }

    @Override
    public double getExposureByCurrency(Currency currency, List<IOrder> orders) {
        double rc = 0;
        for (IOrder order : orders) {
            if (order.getState() == IOrder.State.FILLED) {
                if (order.getInstrument().getPrimaryCurrency() == currency) {
                    double primaryAmount = order.getAmount();
                    if (order.isLong()) {
                        rc = rc + primaryAmount;
                    } else {
                        rc = rc - primaryAmount;
                    }
                }
                if (order.getInstrument().getSecondaryCurrency() == currency) {
                    double secondaryAmount = order.getAmount() * order.getOpenPrice();
                    if (order.isLong()) {
                        rc = rc - secondaryAmount;
                    } else {
                        rc = rc + secondaryAmount;
                    }
                }
            }
        }
        return rc;
    }

    @Override
    public Map<Currency, Double> getExposureByCurrency() throws JFException {
        Map<Currency, Double> rc = new HashMap<Currency, Double>();
        for (IOrder order : engine.getOrders()) {
            if (order.getState() == IOrder.State.FILLED) {
                //calculating primary amount exposure
                Currency primaryCurrency = order.getInstrument().getPrimaryCurrency();
                double primaryAmount = order.getAmount();
                if (!order.isLong()) {
                    primaryAmount = -primaryAmount;
                }
                //store primary amount exposure
                if (rc.containsKey(primaryCurrency)) {
                    double exposure = rc.get(primaryCurrency);
                    rc.put(primaryCurrency, exposure + primaryAmount);
                } else {
                    rc.put(primaryCurrency, primaryAmount);
                }
                //calculating secondary amount exposure
                Currency secondaryCurrency = order.getInstrument().getSecondaryCurrency();
                double secondaryAmount = order.getAmount() * order.getOpenPrice();
                if (order.isLong()) {
                    secondaryAmount = -secondaryAmount;
                }
                //store secondary amount exposure
                if (rc.containsKey(secondaryCurrency)) {
                    double exposure = rc.get(secondaryCurrency);
                    rc.put(secondaryCurrency, exposure + secondaryAmount);
                } else {
                    rc.put(secondaryCurrency, secondaryAmount);
                }

            }
        }
        return rc;
    }

}
